﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;

namespace Faust.Andrew.MTG
{
    /// <summary>
    /// The Importer class will import cards and decks in to the data store off of a tab delimited text file. 
    /// </summary>
    /// <remarks>
    /// The input file format must be in tab delimited format with two columns. The first column must be field name
    /// and the second column is the quantity. If the quantity is invalid it will default to 1.
    /// 
    /// The import process is multi part. First call LoadFile to load the text file and lookup the cards on the Magic
    /// website. This process will populated the Matches collection. The user is then responsible to review the matches
    /// and possibly fix or reject matches that are problematic.
    /// 
    /// When the user is happy with the matches, they can be saved to the database with the SaveCards command.
    /// 
    /// If a deck is being imported, the above steps must still be done. Afterwards a call to CreateDeck will add the deck
    /// records to the database.
    /// </remarks>
    public class Importer
    {
        public event Action<object, ImporterProgressEventArgs> ProgessEvent;

        protected Gatherer _gatherer;
        protected DataStore _dataStore;

        private List<CardMatch> _matches;
        public List<CardMatch> Matches
        {
            get { return _matches; }
        }

        protected void FireProgressEvent(string cardName, int count)
        {
            if (ProgessEvent != null)
            {
                ProgessEvent(this, new ImporterProgressEventArgs(cardName, count));
            }
        }

        public List<Card> ExactMatchCards
        {
            get
            {
                if (_matches == null) { return new List<Card>(); }

                List<Card> cards = new List<Card>();

                var matchCards = from match in _matches
                                 where match.MatchState == MatchStates.Match
                                 select match.ExactMatch;

                cards.AddRange(matchCards);

                return cards;
            }
        }

        public Importer(Gatherer gatherer, DataStore dataStore)
        {
            if (gatherer == null)
                throw new ArgumentNullException("gatherer", "gatherer is null.");
            if (dataStore == null)
                throw new ArgumentNullException("dataStore", "dataStore is null.");

            _gatherer = gatherer;
            _dataStore = dataStore;
        }

        public void LoadFile(string fileName, bool skipFirstLine)
        {
            _matches = new List<CardMatch>();

            using (TextReader reader = File.OpenText(fileName))
            {

                int lineCount = 1;

                //If we need to skip the first line we consume it, but do nothing with it.
                if (skipFirstLine)
                {
                    reader.ReadLine(); 
                    lineCount++;
                }

                string line = null;
                
                //Get each line one at a time from the file
                while ((line = reader.ReadLine()) != null)
                {
                    //If it's an empty line we'll just move one.
                    if (String.IsNullOrEmpty(line)) { continue; }

                    lineCount++;

                    //Split the line in to two fields on the tab character. If the split operation doesn't
                    //result in exactly two segments, the file is improperly formed. Throw an exception and
                    //report the location that is incorret.
                    string[] segments = line.Split('\t');
                    string name = String.Empty;
                    int quantity = 0;
                    string multiverseId = String.Empty;
                    
                    if (segments == null || segments.Length == 0)
                    {
                        throw new Exception(String.Format("Error processing file on line {0}", lineCount));
                    }

                    name = segments[0].Trim();

                    if (segments.Length >= 2)
                    {
                        Int32.TryParse(segments[1], out quantity);                        
                    }

                    if (quantity <= 0) { quantity = 1; }

                    if (segments.Length > 2)
                    {
                        multiverseId = segments[2].Trim();
                    }
                    

                    //Report Progress
                    FireProgressEvent(name, lineCount);

                    //Look up the card information and add the match information
                    LookupCard(name, multiverseId, quantity, false);
                }
            }
        }

        public void LoadApprenticeFile(string fileName)
        {
            _matches = new List<CardMatch>();

            using (TextReader reader = File.OpenText(fileName))
            {

                int lineCount = 1;

                string line = null;

                //Get each line one at a time from the file
                while ((line = reader.ReadLine()) != null)                
                {
                    line = line.Trim();

                    //If it's an empty line we'll just move one.
                    if (String.IsNullOrEmpty(line)) { continue; }

                    //First check for a comment
                    if (line.StartsWith("//")) { continue; }

                    lineCount++;

                    bool isSideboard = false;
                    int quantity = 0;
                    string name = String.Empty;

                    //Check to see if this is a Sideboard card. If so flag it and remove
                    //the sideboard marker
                    if (line.StartsWith("SB:"))
                    {
                        isSideboard = true;
                        line = line.Replace("SB:", "");
                        line = line.Trim();

                        //We don't support Sideboard yet so we will continue
                        continue;
                    }

                    //Get the quantity and name
                    int pos = line.IndexOf(" ");
                    if (pos > 0)
                    {
                        if (!Int32.TryParse(line.Substring(0, pos), out quantity) || quantity < 1)
                        {
                            //If the first part of the line wasn't a valid number or was less than 1 then it's an invalid
                            //line so we continue;
                            continue;
                        }

                        name = line.Substring(pos).Trim();

                        //If no valid name then we continue.
                        if (String.IsNullOrEmpty(name)) { continue; }
                    }
                    else
                    {
                        //If no space it's an invalid line so we skip.
                        continue;
                    }

                    //Report Progress
                    FireProgressEvent(name, lineCount);

                    //Look up the card information and add the match information
                    LookupCard(name, String.Empty, quantity, isSideboard);
                }
            }
        }

        protected void LookupCard(string name, string multiverseId, int count, bool isSideboard)
        {
            CardSearchParameters searchParameters = new CardSearchParameters() { Name = name, Page = 0, UseGatherer = true, MultiverseID = multiverseId };
            CardSearchResults results = _gatherer.FindCards(searchParameters);
            CardMatch match;

            if (results.Cards.Count == 1)
            {
                //Exact Match
                match = new CardMatch(name, results.Cards[0], null, MatchStates.Match, count, isSideboard);
            }
            else if (results.Cards.Count > 1)
            {
                //Multiple Matches
                match = new CardMatch(name, null, results.Cards, MatchStates.Multiple, count, isSideboard);
            }
            else
            {
                //No Match
                match = new CardMatch(name, null, null, MatchStates.NoMatch, count, isSideboard);
            }

            //Add the match to the match collection
            _matches.Add(match);
        }

        /// <summary>
        /// SaveCards writes all the matches that have a status of Match to the database.
        /// </summary>
        /// <param name="updateOwnedQuantity">Flag telling whether or not to update the owned quantity</param>
        /// <remarks>In some cases a user may be importing a deck for which they have already imported the actual cards the deck uses.
        /// In these cases you wouldn't want to have it update the quantity as that would double count ownership of cards.
        /// </remarks>
        public void SaveCards(bool updateOwnedQuantity)
        {
            List<Card> cards = new List<Card>();

            foreach (CardMatch match in _matches)
            {
                //If it's not a valid match we move on
                if (match.MatchState != MatchStates.Match || match.ExactMatch == null) { continue; }

                Card card = match.ExactMatch;
             
                //If the user said not to increment the owned count then we need to set the card quantity to 0
                if (updateOwnedQuantity)
                {
                    card.Quantity = match.Count;
                }
                else
                {
                    card.Quantity = 0;
                }

                cards.Add(card);
            }

            //We now save the resulting list
            _dataStore.AddCards(cards, true);
        }

        public void CreateDeck(string deckName, string description)
        {
            Deck deck = _dataStore.CreateDeck(deckName, description);

            foreach (CardMatch match in _matches)
            {
                if (match.MatchState != MatchStates.Match || match.ExactMatch == null || match.Count < 1) { continue; }

                Card card = match.ExactMatch;

                deck.SetCardQuantity(card, match.Count, true);
            }

            //We now have all the cards assigned to the deck so we write it out.
            _dataStore.UpdateDeck(deck, true);
        }
    }
}